package com.mvc.controller;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.file.Files;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.mvc.core.uploader.config.Configurations;
import com.mvc.core.uploader.config.IoUtil;
import com.mvc.core.uploader.config.Range;
import com.mvc.core.uploader.config.StreamException;
import com.mvc.core.uploader.config.TokenUtil;
/**
 * 上传
 * 
 * @author 625
 *
 */
@Controller
@RequestMapping(value = "/upfile")
public class UploadController {
	private static final long serialVersionUID = -8619685235661387895L;
	static final int BUFFER_LENGTH = 10240;
	static final String START_FIELD = "start";
	public static final String CONTENT_RANGE_HEADER = "content-range";

	static final String FILE_NAME_FIELD = "name";
	static final String FILE_SIZE_FIELD = "size";
	static final String TOKEN_FIELD = "token";
	static final String SERVER_FIELD = "server";
	static final String SUCCESS = "success";
	static final String MESSAGE = "message";

	@GetMapping(value = "/toup")
	public ModelAndView toUp(Model model) {
		ModelAndView mav = new ModelAndView("/upload/index");
		return mav;
	}

	private void doOptions(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		resp.setContentType("application/json;charset=utf-8");
		resp.setHeader("Access-Control-Allow-Headers", "Content-Range,Content-Type");
		resp.setHeader("Access-Control-Allow-Origin", Configurations.getCrossOrigins());
		resp.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
	}

	@GetMapping(value = "/upload")
	public void uploadGet(HttpServletResponse resp, HttpServletRequest req) throws ServletException, IOException {

		doOptions(req, resp);

		final String token = req.getParameter(TOKEN_FIELD);
		final String size = req.getParameter(FILE_SIZE_FIELD);
		final String fileName = req.getParameter(FILE_NAME_FIELD);
		final PrintWriter writer = resp.getWriter();

		/** TODO: validate your token. */

		JSONObject json = new JSONObject();
		long start = 0;
		boolean success = true;
		String message = "";
		try {
			File f = IoUtil.getTokenedFile(token);
			start = f.length();
			/** file size is 0 bytes. */
			if (token.endsWith("_0") && "0".equals(size) && 0 == start)
				f.renameTo(IoUtil.getFile(fileName));
		} catch (FileNotFoundException fne) {
			message = "Error: " + fne.getMessage();
			success = false;
		} finally {
			try {
				if (success)
					json.put(START_FIELD, start);
				json.put(SUCCESS, success);
				json.put(MESSAGE, message);
			} catch (JSONException e) {
			}
			writer.write(json.toString());
			IoUtil.close(writer);
		}
	}

	@PostMapping(value = "/upload")
	public void uploadPost(HttpServletResponse resp, HttpServletRequest req) throws ServletException, IOException {

		doOptions(req, resp);

		final String token = req.getParameter(TOKEN_FIELD);
		final String fileName = req.getParameter(FILE_NAME_FIELD);
		Range range = IoUtil.parseRange(req);

		OutputStream out = null;
		InputStream content = null;
		final PrintWriter writer = resp.getWriter();

		/** TODO: validate your token. */

		JSONObject json = new JSONObject();
		long start = 0;
		boolean success = true;
		String message = "";
		File f = IoUtil.getTokenedFile(token);
		try {
			if (f.length() != range.getFrom()) {
				/** drop this uploaded data */
				throw new StreamException(StreamException.ERROR_FILE_RANGE_START);
			}

			out = new FileOutputStream(f, true);
			content = req.getInputStream();
			int read = 0;
			final byte[] bytes = new byte[BUFFER_LENGTH];
			while ((read = content.read(bytes)) != -1)
				out.write(bytes, 0, read);

			start = f.length();
		} catch (StreamException se) {
			success = StreamException.ERROR_FILE_RANGE_START == se.getCode();
			message = "Code: " + se.getCode();
		} catch (FileNotFoundException fne) {
			message = "Code: " + StreamException.ERROR_FILE_NOT_EXIST;
			success = false;
		} catch (IOException io) {
			message = "IO Error: " + io.getMessage();
			success = false;
		} finally {
			IoUtil.close(out);
			IoUtil.close(content);

			/** rename the file */
			if (range.getSize() == start) {
				/** fix the `renameTo` bug */
				// File dst = IoUtil.getFile(fileName);
				// dst.delete();
				// TODO: f.renameTo(dst); 重命名在Windows平台下可能会失败，stackoverflow建议使用下面这句
				try {
					// 先删除
					IoUtil.getFile(fileName).delete();

					Files.move(f.toPath(), f.toPath().resolveSibling(fileName));
					System.out.println("TK: `" + token + "`, NE: `" + fileName + "`");

					/** if `STREAM_DELETE_FINISH`, then delete it. */
					if (Configurations.isDeleteFinished()) {
						IoUtil.getFile(fileName).delete();
					}
				} catch (IOException e) {
					success = false;
					message = "Rename file error: " + e.getMessage();
				}

			}
			try {
				if (success)
					json.put(START_FIELD, start);
				json.put(SUCCESS, success);
				json.put(MESSAGE, message);
			} catch (JSONException e) {
			}

			writer.write(json.toString());
			IoUtil.close(writer);
		}
	}

	@GetMapping(value = "/tk")
	public void tk(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {

		String name = req.getParameter(FILE_NAME_FIELD);
		String size = req.getParameter(FILE_SIZE_FIELD);
		String token = TokenUtil.generateToken(name, size);

		PrintWriter writer = resp.getWriter();

		JSONObject json = new JSONObject();
		try {
			json.put(TOKEN_FIELD, token);
			if (Configurations.isCrossed())
				json.put(SERVER_FIELD, Configurations.getCrossServer());
			json.put(SUCCESS, true);
			json.put(MESSAGE, "");
		} catch (JSONException e) {
		}
		/** TODO: save the token. */

		writer.write(json.toString());
	}
}
